AWS ALB Ingress Controllerを使ってEKSからALBを作成してみた
AWS上でKubernetesのLoadBalancer Serviceを利用すると、CLB/NLBを簡単に作成することができます。
ですが、特別な理由がない限りはL7の機能が強化されたALBを利用したい場面が多いのではないでしょうか。
ということで今回は、先日EKSでの利用が正式にサポートされたAWS ALB Ingress Controllerを使ってALBを作成する方法を紹介したいと思います。
今回構築する環境のイメージ
今回は以下の作業を実施します。
- EKSクラスタ構築
- AWS ALB Ingress Controllerのデプロイ
- サンプルアプリケーションのデプロイ
- ALB作成
AWS ALB Ingress ControllerによりALBを作成し、サンプルアプリケーションに対しパスベースルーティングができることを確認します。
では早速やっていきましょう!
EKSクラスタ構築
まずはEKSクラスタを構築します。今回はeksctlを利用しサクッと構築していきます。
$ eksctl create cluster --name=k8s-cluster --nodes=2 --node-type=t2.medium
2つのWorkerノードをクラスタ化した状態からスタートします。 kubectlでの実行結果は以下のようになります。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-192-168-21-187.ap-northeast-1.compute.internal Ready <none> 8m v1.11.5 ip-192-168-57-228.ap-northeast-1.compute.internal Ready <none> 8m v1.11.5 $ kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 14m
パブリックサブネットへのタグ追加
ALB Ingress ControllerがALBに使用されるサブネットを自動検出できるようにパブリックサブネットにタグkubernetes.io/role/elb
を付与します。
AWS ALB Ingress Controllerのデプロイ
AWS ALB Ingress Controllerとは
IngressリソースがEKSに登録されたタイミングでALBを作成するモジュールです。
AWS ALB Ingress ControllerはEKSクラスタにPodとして起動させます。コントローラはAPIサーバーと通信し、要件を満たすIngressリソースを見つけるとALBの作成を開始します。
詳細は公式ドキュメント を参照してください。
Workerノードへのポリシー追加
WorkerノードにALBを作成するための権限を付与します。 IAMよりポリシーを作成し、WorkerノードのIAMロールに作成したポリシーをアタッチします。
ServiceAccount作成
AWS ALB Ingress Controllerで利用するServiceAccountを作成し、そのServiceAccountに対しRoleをバインドします。
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.0.0/docs/examples/rbac-role.yaml clusterrole.rbac.authorization.k8s.io/alb-ingress-controller created clusterrolebinding.rbac.authorization.k8s.io/alb-ingress-controller created serviceaccount/alb-ingress created
AWS ALB Ingress Controllerのデプロイ
ローカルにマニュフェストファイルをダウンロードします。
$ curl -sS "https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.0.0/docs/examples/alb-ingress-controller.yaml" > alb-ingress-controller.yaml
–cluster-name
の部分を今回作成したクラスタ名に変更した後、マニュフェストをapplyします。
$ kubectl apply -f alb-ingress-controller.yaml deployment.apps/alb-ingress-controller created
デプロイが完了すると名前空間kube-systemにAWS ALB Ingress ControllerのPodが作成されます。
$ kubectl get pod --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system alb-ingress-controller-77666996cf-ksv4b 1/1 Running 0 3m kube-system aws-node-nmwjv 1/1 Running 1 24m kube-system aws-node-q97ss 1/1 Running 1 24m kube-system coredns-7774b7957b-4kh8p 1/1 Running 0 29m kube-system coredns-7774b7957b-rx8l4 1/1 Running 0 29m kube-system kube-proxy-8gfjx 1/1 Running 0 24m kube-system kube-proxy-kwwf7 1/1 Running 0 24m
サンプルアプリケーションのデプロイ
パスベースルーティングを確認するため簡単なアプリケーションを作成します。作成したアプリケーションはECR経由でEKSにデプロイします。
ECR作成
ECRを作成しECRにログインします。
$ aws ecr create-repository --repository-name eks-test-app { "repository": { "repositoryArn": "arn:aws:ecr:ap-northeast-1:XXXXXXXXXXXX:repository/eks-test-app", "registryId": "XXXXXXXXXXXX", "repositoryName": "eks-test-app", "repositoryUri": "XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app", "createdAt": 1546156701.0 } } $ aws ecr get-login --no-include-email $ docker login -u AWS -p XXXXXX
アプリケーション作成
httpレスポンスとしてPod名を返却するWebサーバーを作成します。まずは/target1
のリクエストに対応するアプリケーションを作成します。
package main import ( "fmt" "log" "net/http" "os" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "healthy!") }) http.HandleFunc("/target1", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "/target1:" + os.Getenv("POD_NAME")) }) log.Fatal(http.ListenAndServe(":8080", nil)) }
ビルド用のDockerfileを作成します。
FROM golang ADD . /go/src/ EXPOSE 8080 CMD ["/usr/local/go/bin/go", "run", "/go/src/server.go"]
タグtarget1
でビルドし、ECRにPushします。
$ docker build -t eks-test-app:target1 . $ docker tag eks-test-app:target1 XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target1 $ docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target1
/target2
のリクエストに対応するアプリケーションも同様に作成し、タグtarget2
でビルド後、ECRにデプロイします。
マニュフェスト作成
アプリケーションをデプロイするためのマニュフェストを作成します。
マニュフェストにはtarget1
,target2
のリクエストに対応するDeploymentおよびServiceを定義します。また、Deploymentで指定するDockerImageには先ほど作成したアプリケーションを指定し、Podの環境変数にはPOD_NAME
を設定します。
apiVersion: v1 kind: Namespace metadata: name: "test-app" --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "test-app-deployment-target1" namespace: "test-app" spec: replicas: 2 template: metadata: labels: app: "test-app-target1" spec: containers: - image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target1 imagePullPolicy: Always name: "test-app-target1" ports: - containerPort: 8080 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: "test-app-deployment-target2" namespace: "test-app" spec: replicas: 2 template: metadata: labels: app: "test-app-target2" spec: containers: - image: XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/eks-test-app:target2 imagePullPolicy: Always name: "test-app-target2" ports: - containerPort: 8080 env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name --- apiVersion: v1 kind: Service metadata: name: "test-app-service-target1" namespace: "test-app" spec: ports: - port: 80 targetPort: 8080 protocol: TCP type: NodePort selector: app: "test-app-target1" --- apiVersion: v1 kind: Service metadata: name: "test-app-service-target2" namespace: "test-app" spec: ports: - port: 80 targetPort: 8080 protocol: TCP type: NodePort selector: app: "test-app-target2"
アプリケーションのデプロイ
アプリケーションをデプロイします。
$ kubectl apply -f test-app.yaml namespace/test-app unchanged deployment.extensions/test-app-deployment-target1 created deployment.extensions/test-app-deployment-target2 created service/test-app-service-target1 created service/test-app-service-target2 created $ kubectl get all --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system pod/alb-ingress-controller-77666996cf-ksv4b 1/1 Running 0 1h kube-system pod/aws-node-nmwjv 1/1 Running 1 1h kube-system pod/aws-node-q97ss 1/1 Running 1 1h kube-system pod/coredns-7774b7957b-4kh8p 1/1 Running 0 1h kube-system pod/coredns-7774b7957b-rx8l4 1/1 Running 0 1h kube-system pod/kube-proxy-8gfjx 1/1 Running 0 1h kube-system pod/kube-proxy-kwwf7 1/1 Running 0 1h test-app pod/test-app-deployment-target1-778f6fddc7-p766m 1/1 Running 0 1m test-app pod/test-app-deployment-target1-778f6fddc7-txkb7 1/1 Running 0 1m test-app pod/test-app-deployment-target2-7589dcc48d-6ptwx 1/1 Running 0 1m test-app pod/test-app-deployment-target2-7589dcc48d-8n5qn 1/1 Running 0 1m NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 1h kube-system service/kube-dns ClusterIP 10.100.0.10 <none> 53/UDP,53/TCP 1h test-app service/test-app-service-target1 NodePort 10.100.38.252 <none> 80:30583/TCP 1m test-app service/test-app-service-target2 NodePort 10.100.124.165 <none> 80:32110/TCP 1m NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE kube-system daemonset.apps/aws-node 2 2 2 2 2 <none> 1h kube-system daemonset.apps/kube-proxy 2 2 2 2 2 <none> 1h NAMESPACE NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE kube-system deployment.apps/alb-ingress-controller 1 1 1 1 1h kube-system deployment.apps/coredns 2 2 2 2 1h test-app deployment.apps/test-app-deployment-target1 2 2 2 2 1m test-app deployment.apps/test-app-deployment-target2 2 2 2 2 1m NAMESPACE NAME DESIRED CURRENT READY AGE kube-system replicaset.apps/alb-ingress-controller-77666996cf 1 1 1 1h kube-system replicaset.apps/coredns-7774b7957b 2 2 2 1h test-app replicaset.apps/test-app-deployment-target1-778f6fddc7 2 2 2 1m test-app replicaset.apps/test-app-deployment-target2-7589dcc48d 2 2 2 1m
ALB作成
ingressのマニュフェストを作成します。ALBはパスベースでターゲットを切り替える設定とします。
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: "ingress" namespace: "test-app" annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing labels: app: test-app spec: rules: - http: paths: - path: /target1 backend: serviceName: "test-app-service-target1" servicePort: 80 - path: /target2 backend: serviceName: "test-app-service-target2" servicePort: 80
マニュフェストをapplyします。
$ kubectl apply -f ingress.yaml
しばらくするとALBおよびターゲットグループが作成されます。
$ aws elbv2 describe-load-balancers { "LoadBalancers": [ { "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:loadbalancer/app/b0a25c21-testapp-ingress-1d16/8e4a9e9c192eae33", "DNSName": "b0a25c21-testapp-ingress-1d16-2135925699.ap-northeast-1.elb.amazonaws.com", "CanonicalHostedZoneId": "Z14GRHDCWA56QT", "CreatedTime": "2018-12-30T08:54:13.480Z", "LoadBalancerName": "b0a25c21-testapp-ingress-1d16", "Scheme": "internet-facing", "VpcId": "vpc-02498dac27677a79a", "State": { "Code": "active" }, "Type": "application", "AvailabilityZones": [ { "ZoneName": "ap-northeast-1c", "SubnetId": "subnet-028193ac333ae6241" }, { "ZoneName": "ap-northeast-1d", "SubnetId": "subnet-0aaf687b20bb26445" }, { "ZoneName": "ap-northeast-1a", "SubnetId": "subnet-0c0df73a4671b923c" } ], "SecurityGroups": [ "sg-0c25b1682e9fd7b72" ], "IpAddressType": "ipv4" } ] } $ aws elbv2 describe-target-groups { "TargetGroups": [ { "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:targetgroup/b0a25c21-ce379bdf1fac9fac389/f0458909d46b1a19", "TargetGroupName": "b0a25c21-ce379bdf1fac9fac389", "Protocol": "HTTP", "Port": 1, "VpcId": "vpc-02498dac27677a79a", "HealthCheckProtocol": "HTTP", "HealthCheckPort": "traffic-port", "HealthCheckEnabled": true, "HealthCheckIntervalSeconds": 15, "HealthCheckTimeoutSeconds": 5, "HealthyThresholdCount": 2, "UnhealthyThresholdCount": 2, "HealthCheckPath": "/", "Matcher": { "HttpCode": "200" }, "LoadBalancerArns": [ "arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:loadbalancer/app/b0a25c21-testapp-ingress-1d16/8e4a9e9c192eae33" ], "TargetType": "instance" }, { "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:targetgroup/b0a25c21-daec225fa0154bfbe17/55129d7eec6da764", "TargetGroupName": "b0a25c21-daec225fa0154bfbe17", "Protocol": "HTTP", "Port": 1, "VpcId": "vpc-02498dac27677a79a", "HealthCheckProtocol": "HTTP", "HealthCheckPort": "traffic-port", "HealthCheckEnabled": true, "HealthCheckIntervalSeconds": 15, "HealthCheckTimeoutSeconds": 5, "HealthyThresholdCount": 2, "UnhealthyThresholdCount": 2, "HealthCheckPath": "/", "Matcher": { "HttpCode": "200" }, "LoadBalancerArns": [ "arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:loadbalancer/app/b0a25c21-testapp-ingress-1d16/8e4a9e9c192eae33" ], "TargetType": "instance" } ] }
最後に正しくルーティングができているか確認してみましょう。
$ kubectl get pod --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system alb-ingress-controller-77666996cf-ksv4b 1/1 Running 0 1h kube-system aws-node-nmwjv 1/1 Running 1 1h kube-system aws-node-q97ss 1/1 Running 1 1h kube-system coredns-7774b7957b-4kh8p 1/1 Running 0 2h kube-system coredns-7774b7957b-rx8l4 1/1 Running 0 2h kube-system kube-proxy-8gfjx 1/1 Running 0 1h kube-system kube-proxy-kwwf7 1/1 Running 0 1h test-app test-app-deployment-target1-778f6fddc7-p766m 1/1 Running 0 27m test-app test-app-deployment-target1-778f6fddc7-txkb7 1/1 Running 0 27m test-app test-app-deployment-target2-7589dcc48d-6ptwx 1/1 Running 0 27m test-app test-app-deployment-target2-7589dcc48d-8n5qn 1/1 Running 0 27m # /target1に対するリクエストはtarget1のpodにルーティングされることを確認 $ curl http://b0a25c21-testapp-ingress-1d16-2135925699.ap-northeast-1.elb.amazonaws.com/target1 /target1:test-app-deployment-target1-778f6fddc7-p766m $ curl http://b0a25c21-testapp-ingress-1d16-2135925699.ap-northeast-1.elb.amazonaws.com/target1 /target1:test-app-deployment-target1-778f6fddc7-txkb7 # /target2に対するリクエストはtarget2のpodにルーティングされることを確認 $ curl http://b0a25c21-testapp-ingress-1d16-2135925699.ap-northeast-1.elb.amazonaws.com/target2 /target2:test-app-deployment-target2-7589dcc48d-8n5qn $ curl http://b0a25c21-testapp-ingress-1d16-2135925699.ap-northeast-1.elb.amazonaws.com/target2 /target2:test-app-deployment-target2-7589dcc48d-6ptwx
リクエストがパス毎に正しくルーティングされることが確認できました!!
さいごに
AWS ALB Ingress Controllerを使ってEKSからALBを作成する方法を紹介しました。
AWS ALB Ingress Controllerを利用するとCFnやTerraformを使わずALBを作成することができます。Kubernetesのマニュフェストという統一されたフォーマットでリソースを作成することで、リソースの管理/運用もしやすくなるのではないでしょうか。
Service Brokerあたりもうまく利用することで、初期構築以外ではCFnやTerraformを利用しない!そんな時代がくるかもしれませんね。